/**
 *  This file is part of Google App Engine suppport in NetBeans IDE.
 *
 *  Google App Engine suppport in NetBeans IDE is free software: you can
 *  redistribute it and/or modify it under the terms of the GNU General
 *  Public License as published by the Free Software Foundation, either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  Google App Engine suppport in NetBeans IDE is distributed in the hope
 *  that it will be useful, but WITHOUT ANY WARRANTY; without even the
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Google App Engine suppport in NetBeans IDE.
 *  If not, see <http://www.gnu.org/licenses/>.
 */
package org.netbeans.modules.j2ee.appengine.editor;

import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.DefaultCellEditor;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import org.netbeans.modules.j2ee.appengine.editor.AppEngineDataModel.Files;
import org.openide.util.Parameters;

/**
 *
 * @author Jindrich Sedek
 */
class DesignTabPanel extends javax.swing.JPanel implements DocumentListener {

    private static final String[] ieTitles = new String[]{
        org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "includes"),
        org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "excludes"),};
    private static final String[] varTitles = new String[]{
        org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "variable_name"),
        org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "variable_value"),};
    private static final String[] propTitles = new String[]{
        org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "property_name"),
        org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "property_value"),};
    private AppEngineDataModel model;

    /** Creates new form DesignTabPanel */
    public DesignTabPanel() {
        initComponents();
        nameField.getDocument().addDocumentListener(this);
        versionField.getDocument().addDocumentListener(this);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        nameField = new javax.swing.JTextField();
        versionField = new javax.swing.JTextField();
        sessionsCheckBox = new javax.swing.JCheckBox();
        sslCheckBox = new javax.swing.JCheckBox();
        jPanel5 = new javax.swing.JPanel();
        jLabel5 = new javax.swing.JLabel();
        jScrollPane3 = new javax.swing.JScrollPane();
        propertiesTable = new javax.swing.JTable();
        sysDrop = new javax.swing.JButton();
        jPanel6 = new javax.swing.JPanel();
        jLabel6 = new javax.swing.JLabel();
        jScrollPane4 = new javax.swing.JScrollPane();
        variablesTable = new javax.swing.JTable();
        envDrop = new javax.swing.JButton();
        jPanel3 = new javax.swing.JPanel();
        jLabel3 = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        staticFilesTable = new javax.swing.JTable();
        jPanel4 = new javax.swing.JPanel();
        jLabel4 = new javax.swing.JLabel();
        jScrollPane2 = new javax.swing.JScrollPane();
        resourceFilesTable = new javax.swing.JTable();

        org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.jLabel1.text")); // NOI18N

        org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, nameField, org.jdesktop.beansbinding.ObjectProperty.create(), jLabel1, org.jdesktop.beansbinding.BeanProperty.create("labelFor"));
        bindingGroup.addBinding(binding);

        org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.jLabel2.text")); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, versionField, org.jdesktop.beansbinding.ObjectProperty.create(), jLabel2, org.jdesktop.beansbinding.BeanProperty.create("labelFor"));
        bindingGroup.addBinding(binding);

        nameField.setText(org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.nameField.text")); // NOI18N

        versionField.setText(org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.versionField.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(sessionsCheckBox, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.sessionsCheckBox.text")); // NOI18N
        sessionsCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                sessionsCheckBoxActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(sslCheckBox, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.sslCheckBox.text")); // NOI18N
        sslCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                sslCheckBoxActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(jLabel5, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.jLabel5.text")); // NOI18N

        propertiesTable.setModel(new DefaultTableModel());
        propertiesTable.setRowHeight(20);
        jScrollPane3.setViewportView(propertiesTable);

        org.openide.awt.Mnemonics.setLocalizedText(sysDrop, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.jButton5.text")); // NOI18N
        sysDrop.setEnabled(false);
        sysDrop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                sysDropActionPerformed(evt);
            }
        });

        org.jdesktop.layout.GroupLayout jPanel5Layout = new org.jdesktop.layout.GroupLayout(jPanel5);
        jPanel5.setLayout(jPanel5Layout);
        jPanel5Layout.setHorizontalGroup(
            jPanel5Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jLabel5, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 306, Short.MAX_VALUE)
            .add(jScrollPane3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 306, Short.MAX_VALUE)
            .add(jPanel5Layout.createSequentialGroup()
                .add(sysDrop)
                .addContainerGap())
        );
        jPanel5Layout.setVerticalGroup(
            jPanel5Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel5Layout.createSequentialGroup()
                .add(jLabel5)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jScrollPane3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 88, Short.MAX_VALUE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(sysDrop))
        );

        org.openide.awt.Mnemonics.setLocalizedText(jLabel6, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.jLabel6.text")); // NOI18N

        variablesTable.setModel(new DefaultTableModel());
        variablesTable.setRowHeight(20);
        jScrollPane4.setViewportView(variablesTable);

        org.openide.awt.Mnemonics.setLocalizedText(envDrop, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.jButton5.text")); // NOI18N
        envDrop.setEnabled(false);
        envDrop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                envDropActionPerformed(evt);
            }
        });

        org.jdesktop.layout.GroupLayout jPanel6Layout = new org.jdesktop.layout.GroupLayout(jPanel6);
        jPanel6.setLayout(jPanel6Layout);
        jPanel6Layout.setHorizontalGroup(
            jPanel6Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel6Layout.createSequentialGroup()
                .add(jLabel6)
                .addContainerGap(155, Short.MAX_VALUE))
            .add(jScrollPane4, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 299, Short.MAX_VALUE)
            .add(jPanel6Layout.createSequentialGroup()
                .add(envDrop)
                .addContainerGap(212, Short.MAX_VALUE))
        );
        jPanel6Layout.setVerticalGroup(
            jPanel6Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel6Layout.createSequentialGroup()
                .add(jLabel6, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 16, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jScrollPane4, 0, 86, Short.MAX_VALUE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(envDrop, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 31, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
        );

        org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.jLabel3.text")); // NOI18N

        staticFilesTable.setModel(new DefaultTableModel());
        staticFilesTable.setRowHeight(20);
        jScrollPane1.setViewportView(staticFilesTable);

        org.jdesktop.layout.GroupLayout jPanel3Layout = new org.jdesktop.layout.GroupLayout(jPanel3);
        jPanel3.setLayout(jPanel3Layout);
        jPanel3Layout.setHorizontalGroup(
            jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(org.jdesktop.layout.GroupLayout.TRAILING, jLabel3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 306, Short.MAX_VALUE)
            .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 306, Short.MAX_VALUE)
        );
        jPanel3Layout.setVerticalGroup(
            jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel3Layout.createSequentialGroup()
                .add(jLabel3)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 99, Short.MAX_VALUE))
        );

        org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(DesignTabPanel.class, "DesignTabPanel.jLabel4.text")); // NOI18N

        resourceFilesTable.setModel(new DefaultTableModel());
        resourceFilesTable.setRowHeight(20);
        jScrollPane2.setViewportView(resourceFilesTable);

        org.jdesktop.layout.GroupLayout jPanel4Layout = new org.jdesktop.layout.GroupLayout(jPanel4);
        jPanel4.setLayout(jPanel4Layout);
        jPanel4Layout.setHorizontalGroup(
            jPanel4Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jLabel4, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 299, Short.MAX_VALUE)
            .add(jScrollPane2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 299, Short.MAX_VALUE)
        );
        jPanel4Layout.setVerticalGroup(
            jPanel4Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel4Layout.createSequentialGroup()
                .add(jLabel4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 16, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jScrollPane2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 99, Short.MAX_VALUE))
        );

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(sessionsCheckBox)
                    .add(sslCheckBox)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(jLabel1)
                            .add(jLabel2))
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(versionField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 505, Short.MAX_VALUE)
                            .add(nameField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 505, Short.MAX_VALUE)))
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(jPanel3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .add(org.jdesktop.layout.GroupLayout.TRAILING, jPanel5, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                        .add(26, 26, 26)
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                            .add(jPanel4, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .add(org.jdesktop.layout.GroupLayout.LEADING, jPanel6, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(20, 20, 20)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(jLabel1)
                    .add(nameField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(jLabel2)
                    .add(versionField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(sessionsCheckBox)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(sslCheckBox)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jPanel4, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(jPanel3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(jPanel5, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, jPanel6, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
        );

        bindingGroup.bind();
    }// </editor-fold>//GEN-END:initComponents

    private void sessionsCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sessionsCheckBoxActionPerformed
        if (model != null) {
            model.setSessionEnabled(sessionsCheckBox.isSelected());
        }
    }//GEN-LAST:event_sessionsCheckBoxActionPerformed

    private void sslCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sslCheckBoxActionPerformed
        if (model != null) {
            model.setSslEnabled(sslCheckBox.isSelected());
        }
    }//GEN-LAST:event_sslCheckBoxActionPerformed

    private void sysDropActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sysDropActionPerformed
        doDropAction(propertiesTable, model.getSystemProperties(), propTitles);
    }//GEN-LAST:event_sysDropActionPerformed

    private void envDropActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_envDropActionPerformed
        doDropAction(variablesTable, model.getEnvVariables(), varTitles);
    }//GEN-LAST:event_envDropActionPerformed

    private void doDropAction(JTable table, Map<String, String> properties, String[] titles) {
        int startIndex = table.getSelectionModel().getMinSelectionIndex();
        int endIndex = table.getSelectionModel().getMaxSelectionIndex();
        if ((startIndex == -1) || (endIndex == -1)) {
            return;
        } else {
            Set<String> propertiesToRemove = new HashSet<String>();
            for (int i = startIndex; i <= endIndex; i++) {
                String selectedProperty = table.getModel().getValueAt(i, 0).toString();
                propertiesToRemove.add(selectedProperty);
            }
            for (String prop : propertiesToRemove) {
                properties.remove(prop);
            }
            model.notifyChangedFilesOrProperties();
            setUpProperties(table, properties, titles);
        }
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton envDrop;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JLabel jLabel6;
    private javax.swing.JPanel jPanel3;
    private javax.swing.JPanel jPanel4;
    private javax.swing.JPanel jPanel5;
    private javax.swing.JPanel jPanel6;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JScrollPane jScrollPane3;
    private javax.swing.JScrollPane jScrollPane4;
    private javax.swing.JTextField nameField;
    private javax.swing.JTable propertiesTable;
    private javax.swing.JTable resourceFilesTable;
    private javax.swing.JCheckBox sessionsCheckBox;
    private javax.swing.JCheckBox sslCheckBox;
    private javax.swing.JTable staticFilesTable;
    private javax.swing.JButton sysDrop;
    private javax.swing.JTable variablesTable;
    private javax.swing.JTextField versionField;
    private org.jdesktop.beansbinding.BindingGroup bindingGroup;
    // End of variables declaration//GEN-END:variables

    public void insertUpdate(DocumentEvent e) {
        updateFields();
    }

    public void removeUpdate(DocumentEvent e) {
        updateFields();
    }

    public void changedUpdate(DocumentEvent e) {
        updateFields();
    }

    private void updateFields() {
        if (model == null) {
            return;
        }
        model.setAppName(nameField.getText());
        model.setAppVersion(versionField.getText());
    }

    public void setModel(AppEngineDataModel model) {
        Parameters.notNull("appengine model should be never null", model);
        this.model = null;
        versionField.setText(model.getAppVersion());
        nameField.setText(model.getAppName());
        sessionsCheckBox.setSelected(model.getSessionEnabled());
        sslCheckBox.setSelected(model.getSslEnabled());
        setTableModel(staticFilesTable, model.getStaticFiles());
        setTableModel(resourceFilesTable, model.getResourceFiles());
        setUpProperties(propertiesTable, model.getSystemProperties(), propTitles);
        propertiesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent e) {
                if (propertiesTable.getSelectionModel().isSelectionEmpty()) {
                    sysDrop.setEnabled(false);
                } else {
                    sysDrop.setEnabled(true);
                }
            }
        });
        setUpProperties(variablesTable, model.getEnvVariables(), varTitles);
        variablesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent e) {
                if (variablesTable.getSelectionModel().isSelectionEmpty()) {
                    envDrop.setEnabled(false);
                } else {
                    envDrop.setEnabled(true);
                }
            }
        });
        this.model = model;
    }

    private void setTableModel(JTable filesTable, final Files files) {
        FilesTableModel ftm = new FilesTableModel(filesTable, files);
        filesTable.setModel(ftm);
    }

    private class FilesTableModel extends CorrectFocusAvareTableModel {

        private final Files files;

        public FilesTableModel(JTable table, Files files) {
            super(table);
            this.files = files;
        }

        public int getRowCount() {
            return files.excludes.size();
        }

        public int getColumnCount() {
            return 2;
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            if (columnIndex == 0) {
                return files.includes.get(rowIndex);
            } else if (columnIndex == 1) {
                return files.excludes.get(rowIndex);
            } else {
                assert false : "getValueAt" + columnIndex;
            }
            return null;
        }

        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            String previous = null;
            if (columnIndex == 0) {
                previous = files.includes.set(rowIndex, aValue.toString());
            } else if (columnIndex == 1) {
                previous = files.excludes.set(rowIndex, aValue.toString());
            } else {
                assert false : "getValueAt" + columnIndex;
            }
            if (!aValue.toString().equals(previous)) {
                model.notifyChangedFilesOrProperties();
            }
        }

        @Override
        public String getColumnName(int column) {
            return ieTitles[column];
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return true;
        }
    }

    private void setUpProperties(JTable propertiesTable, final Map<String, String> propertiesMap, final String[] columnTitles) {
        PropertiesTableModel propsModel = new PropertiesTableModel(propertiesTable, propertiesMap, columnTitles);
        propertiesTable.setModel(propsModel);
        propsModel.fireTableDataChanged();
    }

    private class PropertiesTableModel extends CorrectFocusAvareTableModel {

        private static final String EMPTY_STRING = "";
        final String[] titles;
        final Map<String, String> propertiesMap;
        final List<String> keys;

        private PropertiesTableModel(JTable table, Map<String, String> propertiesMap, String[] titles) {
            super(table);
            this.titles = titles;
            this.propertiesMap = propertiesMap;
            this.keys = new ArrayList<String>(propertiesMap.keySet());
            keys.add(EMPTY_STRING);
        }

        public int getRowCount() {
            return keys.size();
        }

        public int getColumnCount() {
            return 2;
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            if (rowIndex == propertiesMap.size()) {
                return EMPTY_STRING;
            }
            if (columnIndex == 0) {
                return keys.get(rowIndex);
            } else if (columnIndex == 1) {
                return propertiesMap.get(keys.get(rowIndex));
            } else {
                assert false : "getValueAt" + columnIndex;
            }
            return null;
        }

        @Override
        public void setValueAt(Object newItem, int rowIndex, int columnIndex) {
            if (columnIndex == 0) {
                String newKey = newItem.toString();
                String oldKey = keys.get(rowIndex);
                if (!newKey.equals(oldKey)) {
                    String value = propertiesMap.remove(oldKey);
                    keys.set(rowIndex, newKey);
                    propertiesMap.put(newKey, value);
                    if (keys.size() < propertiesMap.size() + 1) {
                        keys.add(EMPTY_STRING);
                    }
                    model.notifyChangedFilesOrProperties();
                }
            } else if (columnIndex == 1) {
                String key = keys.get(rowIndex);
                String previous = propertiesMap.put(key, newItem.toString());
                if (!newItem.toString().equals(previous)) {
                    model.notifyChangedFilesOrProperties();
                }
            } else {
                assert false : "setValueAt" + columnIndex;
            }
        }

        @Override
        public String getColumnName(int column) {
            return titles[column];
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return true;
        }
    }

    private abstract class CorrectFocusAvareTableModel extends AbstractTableModel implements FocusListener {

        private final JTable table;
        private final JTextField field = new JTextField();

        public CorrectFocusAvareTableModel(final JTable table) {
            this.table = table;
            field.addFocusListener(this);
            table.setDefaultEditor(String.class, new DefaultCellEditor(field));
        }

        public void focusGained(FocusEvent e) {
        }

        public void focusLost(FocusEvent e) {
            stopEditing(table);
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return String.class;
        }
    }

    void stopEditing() {
        stopEditing(propertiesTable);
        stopEditing(variablesTable);
        stopEditing(resourceFilesTable);
        stopEditing(staticFilesTable);
    }

    private static void stopEditing(JTable table) {
        TableCellEditor tc = table.getCellEditor();
        if (tc != null) {
            tc.stopCellEditing();
        }
    }
}
